Post

Replies

Boosts

Views

Activity

How do I handle Ask to Buy or retried purchases with Price Tiers?
I work on an e-reader app that sells books as IAPs. We have a lot - about 20,000 or so. This is over Apple's limit of 10,000 IAPs, so we've had to ask them to raise the limit for us more than once. Recently, at Apple's suggestion, we finally switched to a mechanism called Price Tiers, where instead of having one IAP item in App Store Connect for each of our products, we have one item per price level, so if the user buys product ID (in our system) 12345 for $5, instead of buying non-consumable IAP ID com.mycompany.myapp.12345, they buy consumable IAP ID com.mycompany.myapp.price_tier.5. In our system, we link the purchase to product 12345 at the time of receipt verification and everything works great. But I have an issue that manifests when the user is using Ask to Buy or when receipt validation fails in our back-end, I don't call finishTransaction, and the transaction is retried later. The sequence is this: User buys product 12345, which is com.mycompany.myapp.price_tier.5 The user has Ask to Buy enabled, so the transaction ends in the deferred state, and the IAP flow on the device is done for now At some point later, the user gets permission for the purchase, and my SKPaymentQueue delegate gets notified of the transaction again in the background But at the point of step 3, I have no way of knowing what product in our system the transaction is for. The same issue occurs if receipt verification fails on our end - I don't call finishTransaction, so StoreKit retries it later, but again at that point I don't know which one of our products to register to the user's account. How do I make Ask to Buy (or any situation where I get notified of a transaction outside of a user-driven purchase) work with Price Tiers?
0
0
771
Nov ’21
How can I choose what scene gets opened on iPad after all are closed?
I have an iPad app in which I'm starting to support multiple windows / scenes. I have one main window type, let's say MainScene, and at least one secondary window type for opening specific types of content, say DetailScene. I have not declared my scene types in Info.plist. I have implemented application:configurationForConnectingSceneSession:options: like this: -(UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { NSUserActivity *activity = options.userActivities.anyObject; NSString *activityType = activity.activityType; if ([activityType isEqualToString:@"detailType"]) return [DetailSceneDelegate makeSceneConfiguration]; return [MainSceneDelegate makeSceneConfiguration]; } Say I perform these steps: Launch app for the first time. I get a call to configurationForConnectingSceneSession, and the activity type is nil so it returns a MainScene. Open a new window for some piece of content. That uses the detail scene activity type, so configurationForConnectingSceneSession returns a DetailScene. Creating the new scene looks like this: NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:@"detailType"]; activity.userInfo = @{@"content_id": @(contentRowId)}; [[UIApplication sharedApplication] requestSceneSessionActivation:nil userActivity:activity options:nil errorHandler:nil]; Suspend the app and open the app switcher. Discard (by flicking up) first the main window and then the detail window. The app is now killed. Relaunch the app. At this point I do not get a call to configurationForConnectingSceneSession. I get the detail scene back, restored from its user activity, with calls straight to DetailSceneDelegate. My question: how do I control what scene gets restored in this situation? I want my main scene to come back. Messages and Mail and Notes all do this. If you open Messages and drag a conversation out into a new window, you get a window for that conversation with a Done button in the corner that will dismiss the window. If you perform my steps above with Messages, you will relaunch to the full Messages view. Are they converting the detail view to a main view on the fly? Or is there a way to tell the system that the detail scene is secondary and should not be restored first, or that I should get asked what I want to restore via configurationForConnectingSceneSession? Or something else?
2
0
1.4k
Mar ’22
How can I integrate my own text changes into UITextView's undo manager?
I have an app that uses UITextView for some text editing. I have some custom operations I can do on the text that I want to be able to undo, and I'm representing those operations in a way that plugs into NSUndoManager nicely. For example, if I have a button that appends an emoji to the text, it looks something like this: func addEmoji() { let inserting = NSAttributedString(string: "😀") self.textStorage.append(inserting) let len = inserting.length let range = NSRange(location: self.textStorage.length - len, length: len) self.undoManager?.registerUndo(withTarget: self, handler: { view in view.textStorage.deleteCharacters(in: range) } } My goal is something like this: Type some text Press the emoji button to add the emoji Trigger undo (via gesture or keyboard shortcut) and the emoji is removed Trigger undo again and the typing from step 1 is reversed If I just type and then trigger undo, the typing is reversed as you'd expect. And if I just add the emoji and trigger undo, the emoji is removed. But if I do the sequence above, step 3 works but step 4 doesn't. The emoji is removed but the typing isn't reversed. Notably, if step 3 only changes attributes of the text, like applying a strikethrough to a selection, then the full undo chain works. I can type, apply strikethrough, undo strikethrough, and undo typing. It's almost as if changing the text invalidates the undo manager's previous operations? How do I insert my own changes into UITextView's NSUndoManager without invalidating its chain of other operations?
4
0
1.7k
Dec ’24
How can I replicate CALayer.maskedCorners with UIButtonConfiguration and UIBackgroundConfiguration?
WWDC 2021 session 10064 introduced UIButtonConfiguration, a new way to configure the visual appearance of buttons, for iOS 15. I'm now in the process of switching all my button setup over to UIButtonConfiguration, and I've run into one situation where it doesn't apply: rounding specific corners of the button. CALayer has the maskedCorners property that lets you round or mask specific corners but not others. In transferring border radius, border width, and border color styling over to UIButtonConfiguration via UIBackgroundConfiguration, I don't see an equivalent UI. Am I missing something? For now, in cases when I need the maskedCorners functionality, I'm falling back to using the layer for border properties.
Topic: UI Frameworks SubTopic: UIKit Tags:
1
0
716
Jun ’23
How can I control image sizing with UIButtonConfiguration?
WWDC 2021 session 10064 introduced UIButtonConfiguration, a new way to configure the visual appearance of buttons, for iOS 15. I've run into a surprising bit of behavior with its image setup: if you give it an image from an asset catalog that's bigger than the button's bounds, the image is sized too big for the button. See the attached screenshot of an example app. The upper play icon is a button set up with setImage:forState:, and it sizes the image down as expected. The lower one is set up with UIButtonConfiguration, and is constrained to the same size (both buttons have a red border), but its image is too big. How do I control the image sizing behavior here? FB12358840 if any engineers look at this and think there could be an API improvement here.
Topic: UI Frameworks SubTopic: UIKit Tags:
0
0
736
Jun ’23
Why does NSAttributedString's NSItemProviderWriting implementation not transfer custom attributes that implement Codable?
I am working on supporting some formatted text editing in my app, and I've been experimenting with copy and paste support for formatted text. I discovered that NSAttributedString implements NSItemProviderWriting, which means I can give it to UIPasteboard via setObjects and all the built-in attributes transfer perfectly if I then paste it into another text view, or even another app that behaves itself. But if I have custom attributes in my attributed string, having their values implement Codable doesn't let them transfer across the clipboard. In my implementation of textPasteConfigurationSupporting(_: transform:), I try to get an attributed string like this: let attr = item.itemProvider.loadObject(ofClass: NSAttributedString.self) { val, err in //...handle here } I get an error like this: Error Domain=NSItemProviderErrorDomain Code=-1000 "Cannot load representation of type com.apple.uikit.attributedstring" UserInfo={NSLocalizedDescription=Cannot load representation of type com.apple.uikit.attributedstring, NSUnderlyingError=0x600003e7bea0 {Error Domain=NSCocoaErrorDomain Code=260 "The file “b036c42113e34c2f9d9af14d6fefcbd534f627d6” couldn’t be opened because there is no such file." UserInfo={NSURL=file:///Users/username/Library/Developer/CoreSimulator/Devices/86E8BDD4-B6AA-4170-B0EB-57C74EC7DDF0/data/Library/Caches/com.apple.Pasteboard/eb77e5f8f043896faf63b5041f0fbd121db984dd/b036c42113e34c2f9d9af14d6fefcbd534f627d6, NSFilePath=/Users/username/Library/Developer/CoreSimulator/Devices/86E8BDD4-B6AA-4170-B0EB-57C74EC7DDF0/data/Library/Caches/com.apple.Pasteboard/eb77e5f8f043896faf63b5041f0fbd121db984dd/b036c42113e34c2f9d9af14d6fefcbd534f627d6, NSUnderlyingError=0x600003e7ac70 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}}} But I tried making my custom attribute values implement NSSecureCoding, and then it worked. Why is Codable conformance not enough here? Is it because the code that serializes and deserializes is still in Objective-C and isn't aware of Codable? Will this change as the open-source Foundation in Swift work continues?
1
0
1.1k
Jun ’24
Is AAAttribution.attributionToken safe for the main thread?
I'm seeing some hang reports for my app in the Xcode organizer that boil down to [AAAttribution attributionTokenWithError:]. The docs for that method talk a lot about a request to Apple's services, but it looks like that's about passing the fetched token up to be decoded...but is the method also making a network request? Or is it doing something else that shouldn't be done on the main thread? If it wasn't main-thread safe I'd expect it to be documented as such, or for the Swift version to be async...
0
0
948
Oct ’23
How can I put an NSToolbarItem on the leading edge of an NSToolbar in Catalyst?
I'm working to make my iOS app available via Catalyst, and I'm adding a leading-edge sidebar via UISplitViewController and putting a toggle button in the window toolbar to control it. I'd like that sidebar toggle button to go on the leading side of the toolbar, before the window title. In the Adding a Toolbar Catalyst tutorial, there are screenshots of this behavior: But when I download the finished project and run it on my machine (macOS 14.4), the toolbar buttons are clustered on the trailing edge: How do I achieve the behavior shown in the tutorial's screenshot, where the toggle sidebar button (or, ideally any custom toolbar item I choose) shows up on the leading edge? Even more ideally, is there a way I can make the sidebar toggle button show up in the header section for the sidebar, like it does in Xcode - right next to the stoplight buttons?
1
0
805
May ’24
How can I make NSToolbar on Catalyst check canPerformAction on UISplitViewController's secondary view controller instead of primary?
I have a Catalyst app that I'm adding a sidebar to via UISplitViewController. I have a toolbar on the window with buttons that I want to be enabled or disabled based on the state of the view controller in the split view's secondary column. But it seems to want to check the primary view controller instead. In the Catalyst tutorial Adding a Toolbar, this exact approach is demonstrated. The RecipeDetailViewController has methods for toggleFavorite and editRecipe, and the toolbar items are set up to reference those selectors in the ToolbarDelegate class. In the screenshots in the tutorial, the buttons are shown as enabled based on RecipeDetailViewController.canPerformAction. But when I download and run the complete project on my computer (macOS 14.4.1), the toolbar items are disabled. And if I add the methods to the RecipeListViewController (which is in the primary column of the UISplitViewController, the toolbar items get enabled. Is there a way to make the system ask the correct split view column for canPerformAction? Or is this a bug?
1
0
711
Sep ’24
How can I include app intents only for newer OS versions?
I have an app that currently supports as low as iOS 16. I'd like to add some app intents to it that allow customization using the WidgetConfigurationIntent API that's only available on iOS 17 and later. Is there a way to build an intent (or other kind of app extension) that requires a higher OS version than my main app's deployment target, and only surface it for those OS versions?
0
0
563
Jun ’24
How can I use the F8 play/pause key to control media playback in Catalyst?
I have a Catalyst app that plays audio via AVQueuePlayer, and I'd like to use the system play/pause key (F8 on my MacBook Pro keyboard) to play and pause it. It doesn't seem to work automatically, and if I hook up a UIKeyCommand using UIKeyInputF8, it works with Fn-F8, not F8 on its own. It does seem to work in Overcast's Mac app, but I think that's an iPad app for Mac, not Catalyst, so it's probably going through whatever system pathway that the Lock Screen controls would be using on iOS. How do I make this work on Catalyst?
1
0
695
Aug ’24
What is ChronoKit.InteractiveWidgetActionRunner.Errors Code 1?
This is a follow up to this post about building a Control Center widget to open the app directly to a particular feature. I have it working in a sample app, but when I do the same thing in my full app I get this error: [[com.olivetree.BR-Free::com.olivetree.BR-Free.VerseWidget:com.olivetree.BR-Free.ContinueReadingPlanControl:-]] Control action: failed with error: Error Domain=ChronoKit.InteractiveWidgetActionRunner.Errors Code=1 "(null)" Google has nothing for any of that. Can anyone shed light on what it means? This is my control and its action: @available(iOS 18.0, *) struct ContinueReadingPlanControl : ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration(kind: "com.olivetree.BR-Free.ContinueReadingPlanControl") { ControlWidgetButton(action: ContinueReadingPlanIntent()) { Image(systemName: "book") } } .displayName("Continue Reading Plan") } } @available(iOS 18.0, *) struct ContinueReadingPlanIntent : ControlConfigurationIntent { static let title: LocalizedStringResource = "Continue Reading Plan" static let description = IntentDescription(stringLiteral: "Continue the last-used reading plan") static let isDiscoverable = false static let opensAppWhenRun: Bool = true @MainActor func perform() async throws -> some IntentResult & OpensIntent { let strUrl = "olivetree://startplanday" UserDefaults.standard.setValue(strUrl, forKey: "StartupUrl") return .result(opensIntent: OpenURLIntent(URL(string: strUrl)!)) } } Note also that I'm pulling this from Console.app, streaming the logs from my device. I don't know of a way to debug a Control Center widget in Xcode, though this thread implies that it's possible.
2
0
723
Sep ’24
How can I make my multi-window Catalyst app restore window size and position after closing with stoplight button?
I have a Catalyst app that supports multiple scenes / windows. It has one "main" window type and lots of other secondary windows that can be opened. I'm using the system window restoration functionality via NSQuitAlwaysKeepsWindows to restore windows with their user activity data after the app is quit and restarted. Normally, this works great. If I open my app, change the size and position of my window, quit, and reopen it, the window size and position comes back as expected. But if I close the window using the red stoplight button and then click the app icon to bring it back, it comes back at the default position and size. This isn't how other system apps work - if I close the system Calendar app with the stoplight button, it comes back at the same size and position. How do I get this behavior with my Catalyst app? Is there some identifier property I need to set somewhere? I don't see such a property on UISceneConfiguration. If it matters, I'm using the configurationForConnectingSceneSession method to configure my windows when they open, instead of setting it up in the Info.plist.
1
0
546
Oct ’24
Is there a way to opt a Catalyst app into supporting preferred text size?
As of macOS Sequoia 15.1 (and probably earlier), in System Settings under Accessibility -> Display, there's a Text Size option that looks an awful lot like Dynamic Type on iOS: I have an iOS app with robust support for Dynamic Type that I've brought to the Mac via Catalyst. Is there any way for me to opt this app into supporting this setting, maybe with some Info.plist key? Calendar's Info.plist has a CTIgnoreUserFonts value set to true, but the Info.plist for Notes has no such value.
0
0
572
Oct ’24